package unsafe_lru

import (
	"container/list"

	"bulgat.top/go_cache/cache"
)

type Cache struct {
	capacity        int64
	size            int64
	dList           *list.List
	keyNode         map[string]*list.Element
	deletedCallBack func(key string, value cache.Value)
}

type entry struct {
	key   string
	value cache.Value
}

func New(capacity int64, deletedCallBack func(key string, value cache.Value)) *Cache {
	return &Cache{
		capacity:        capacity,
		size:            0,
		dList:           list.New(),
		keyNode:         make(map[string]*list.Element),
		deletedCallBack: deletedCallBack,
	}
}

func (c *Cache) Get(key string) (value cache.Value, exists bool) {
	if node, exists := c.keyNode[key]; exists {
		c.dList.MoveToFront(node)
		value = node.Value.(*entry).value
		return value, exists
	}
	return
}

func (c *Cache) Put(key string, value cache.Value) (err error) {
	if node, exists := c.keyNode[key]; exists { // 存在
		ety := node.Value.(*entry)
		c.size += value.Len() - ety.value.Len()
		ety.value = value
		c.dList.MoveToFront(node)
	} else {
		element := c.dList.PushFront(&entry{key, value})
		c.keyNode[key] = element
		c.size += value.Len() + int64(len(key))
	}
	for c.capacity < c.size {
		c.RemoveOldest()
	}
	return
}

// RemoveOldest removes the oldest item
func (c *Cache) RemoveOldest() {
	ele := c.dList.Back()
	if ele != nil {
		c.dList.Remove(ele)
		ety := ele.Value.(*entry)
		delete(c.keyNode, ety.key)
		c.size -= int64(len(ety.key)) + ety.value.Len()
		if c.deletedCallBack != nil {
			c.deletedCallBack(ety.key, ety.value)
		}
	}
}

func (c *Cache) Delete(key string) (value cache.Value) {
	if node, exists := c.keyNode[key]; exists {
		ety := node.Value.(*entry)
		value = ety.value
		c.dList.Remove(node)
		delete(c.keyNode, key)
		c.size -= value.Len() + int64(len(key))
	}
	return
}

func (c *Cache) Capacity() int64 {
	return c.capacity
}

func (c *Cache) Size() int64 {
	return c.size
}

func (c *Cache) KeysLen() int64 {
	return int64(c.dList.Len())
}
